package com.tool.emulator.mos6502;

import com.tool.emulator.apple2.runtime.EmuState;
import com.tool.emulator.common.AccessUnit;
import com.tool.emulator.common.AutoDevice;
import com.tool.emulator.mos6502.instrs.Instr;

/**
 * CPU6502.java @author tl 2011-6-18
 */
public class CPU6502 extends AutoDevice implements Constants6502 {
	private final AccessUnit[] mem;
	private final BUS6502 bus;
	public boolean supportD = true;
	private transient Registers regs = new DefaultRegisters();
	private final AccessUnit acc = new AccessUnit() {
		@Override
		public int get() {
			return regs.getAC();
		}

		@Override
		public void set(int b) {
			regs.setAC(b);
		}
	};

	public CPU6502(BUS6502 bus) {
		super("CPU6502", CPU_FREQ);
		this.bus = bus;
		this.mem = bus.getMemory();
	}

	@Override
	public void deviceSaveState(EmuState emuState) throws Exception {
		super.deviceSaveState(emuState);
		//map.put(Registers.class, regs.copy());

		emuState.putDeviceRegistersState(this, regs.copy());

	}

	@Override
	public void deviceLoadState(EmuState emuState) throws Exception {
		super.deviceLoadState(emuState);
		//Registers regs1 = (Registers) map.get(Registers.class);
		Registers regs1 = emuState.getDeviceRegistersState(this);
		this.regs.setValues(regs1);
	}

	public BUS6502 getBUS() {
		return this.bus;
	}

	public AccessUnit[] getMemory() {
		return mem;
	}

	public int noise() {
		int cycles = (int) this.getCycles();
		cycles ^= cycles >> 8;
		cycles ^= cycles >> 8;
		cycles ^= cycles >> 8;
		return 0xff & cycles;
	}

	public synchronized Registers getRegisters() {
		return this.regs;
	}

	public synchronized void setRegisters(Registers regs) {
		regs.getClass();//check null
		this.regs = regs;
	}

	@Override
	public String toString() {
		return regs.toString(this);
	}

	public void interrupt(int mask) {
		regs.setIR(regs.getIR() | mask);
	}

	public void interruptReset(int mask) {
		regs.setIR(regs.getIR() & (~(mask & 0xffff)));
	}

	@Override
	protected void deviceInit() throws Exception {
		super.deviceInit();
		regs.setIR(IR_RESET);
	}

	public int getIR() {
		if (regs.isI())
			return regs.getIR() & (IR_RESET | IR_NMI);
		else
			return regs.getIR();
	}

	//使用同步会影响模拟速度，不同步在数据恢复时会有些不确定的问题。
	@Override
	protected synchronized boolean deviceStep() {
		int code = this.nextCode();
		this.current_cycles += this.run_code(code);
		return true;
	}

	public int currentCode() {
		int ir;
		if ((ir = this.getIR()) == 0)
			return this.read(regs.getPC());
		if (0 != (ir & IR_RESET))
			return Instr.RESET;
		if (0 != (ir & IR_NMI))
			return Instr.BRK_NMI;
		return Instr.BRK_HARD;
	}

	public int nextCode() {
		int ir;
		if ((ir = this.getIR()) == 0)
			return this.next();
		if (0 != (ir & IR_RESET))
			return Instr.RESET;
		if (0 != (ir & IR_NMI))
			return Instr.BRK_NMI;
		return Instr.BRK_HARD;
	}

	private AccessUnit mem(int addr) {
		try {
			return mem[addr];
		} catch (IndexOutOfBoundsException e) {
			System.err.println(e);
			return mem[addr & 0xffff];
		}
	}

	int read(int addr) {
		return mem(addr).get();
	}

	int read2(int addr) {
		return read(addr) + (read(addr + 1) << 8);
	}

	void write(int addr, int val) {
		mem(addr).set(val);
	}

	int next() {
		return nextUnit().get();
	}

	AccessUnit nextUnit() {
		int pc = regs.getPC();
		regs.setPC(pc + 1);
		return mem[pc];
	}

	int next2() {
		return next() | (next() << 8);
	}

	private AccessUnit imm() {
		return nextUnit();
	}

	private AccessUnit abs() {
		return mem(next2());
	}

	private AccessUnit mem1(int addr, int append) {
		if ((addr & 0xff) + append >= 256) this.current_cycles++;
		return mem(addr + append);
	}

	private AccessUnit absx() {
		return mem(next2() + this.regs.getXR());
	}

	private AccessUnit absx1() {
		return mem1(next2(), this.regs.getXR());
	}

	private AccessUnit absy() {
		return mem(next2() + this.regs.getYR());
	}

	private AccessUnit absy1() {
		return mem1(next2(), this.regs.getYR());
	}

	private AccessUnit zpg() {
		return mem(next());
	}

	private AccessUnit zpgx() {
		return mem(0xff & (next() + this.regs.getXR()));
	}

	private AccessUnit zpgy() {
		return mem(0xff & (next() + this.regs.getYR()));
	}

	private AccessUnit ix() {
		int zp = 0xff & (next() + this.regs.getXR());
		return mem(read(zp) + (read(0xff & (zp + 1)) << 8));
	}

	private AccessUnit iy() {
		int zp = next();
		return mem(this.regs.getYR() + read(zp) + (read(0xff & (zp + 1)) << 8));
	}

	private AccessUnit iy1() {
		int zp = next();
		return mem1(read(zp) + (read(0xff & (zp + 1)) << 8), this.regs.getYR());
	}

	private void set_sign_zero(int val) {
		regs.setN(0 != (val & 0x80));
		regs.setZ(val == 0);
	}

	private int run_code(int code) {
		switch (code) {
			case Instr.RESET:
				return this.reset();
			case Instr.BRK_NMI:
				return this.brk(false, CPU6502.NMI_ADDR);
			case Instr.BRK_HARD:
				return this.brk(false, CPU6502.IRQ_ADDR);
			case 0x0:
				return brk(true, IRQ_ADDR);
			case 0x1:
				ora(ix());
				return 6;
			case 0x5:
				ora(zpg());
				return 3;
			case 0x6:
				asl(zpg());
				return 5;
			case 0x8:
				php();
				return 3;
			case 0x9:
				ora(imm());
				return 2;
			case 0xA:
				asl(acc);
				return 2;
			case 0xD:
				ora(abs());
				return 4;
			case 0xE:
				asl(abs());
				return 6;
			case 0x10:
				return bpl();
			case 0x11:
				ora(iy1());
				return 5;
			case 0x15:
				ora(zpgx());
				return 4;
			case 0x16:
				asl(zpgx());
				return 6;
			case 0x18:
				clc();
				return 2;
			case 0x19:
				ora(absy1());
				return 4;
			case 0x1D:
				ora(absx1());
				return 4;
			case 0x1E:
				asl(absx());
				return 7;
			case 0x20:
				jsr();
				return 6;
			case 0x21:
				and(ix());
				return 6;
			case 0x24:
				bit(zpg());
				return 3;
			case 0x25:
				and(zpg());
				return 3;
			case 0x26:
				rol(zpg());
				return 5;
			case 0x28:
				plp();
				return 4;
			case 0x29:
				and(imm());
				return 2;
			case 0x2A:
				rol(acc);
				return 2;
			case 0x2C:
				bit(abs());
				return 4;
			case 0x2D:
				and(abs());
				return 4;
			case 0x2E:
				rol(abs());
				return 6;
			case 0x30:
				return bmi();
			case 0x31:
				and(iy1());
				return 5;
			case 0x35:
				and(zpgx());
				return 4;
			case 0x36:
				rol(zpgx());
				return 6;
			case 0x38:
				sec();
				return 2;
			case 0x39:
				and(absy1());
				return 4;
			case 0x3D:
				and(absx1());
				return 4;
			case 0x3E:
				rol(absx());
				return 7;
			case 0x40:
				rti();
				return 6;
			case 0x41:
				eor(ix());
				return 6;
			case 0x45:
				eor(zpg());
				return 3;
			case 0x46:
				lsr(zpg());
				return 5;
			case 0x48:
				pha();
				return 3;
			case 0x49:
				eor(imm());
				return 2;
			case 0x4A:
				lsr(acc);
				return 2;
			case 0x4C:
				jmp_abs();
				return 3;
			case 0x4D:
				eor(abs());
				return 4;
			case 0x4E:
				lsr(abs());
				return 6;
			case 0x50:
				return bvc();
			case 0x51:
				eor(iy1());
				return 5;
			case 0x55:
				eor(zpgx());
				return 4;
			case 0x56:
				lsr(zpgx());
				return 6;
			case 0x58:
				cli();
				return 2;
			case 0x59:
				eor(absy1());
				return 4;
			case 0x5D:
				eor(absx1());
				return 4;
			case 0x5E:
				lsr(absx());
				return 7;
			case 0x60:
				rts();
				return 6;
			case 0x61:
				adc(ix());
				return 6;
			case 0x65:
				adc(zpg());
				return 3;
			case 0x66:
				ror(zpg());
				return 5;
			case 0x68:
				pla();
				return 4;
			case 0x69:
				adc(imm());
				return 2;
			case 0x6A:
				ror(acc);
				return 2;
			case 0x6C:
				jmp_indrect();
				return 5;
			case 0x6D:
				adc(abs());
				return 4;
			case 0x6E:
				ror(abs());
				return 6;
			case 0x70:
				return bvs();
			case 0x71:
				adc(iy1());
				return 5;
			case 0x75:
				adc(zpgx());
				return 4;
			case 0x76:
				ror(zpgx());
				return 6;
			case 0x78:
				sei();
				return 2;
			case 0x79:
				adc(absy1());
				return 4;
			case 0x7D:
				adc(absx1());
				return 4;
			case 0x7E:
				ror(absx());
				return 7;
			case 0x81:
				sta(ix());
				return 6;
			case 0x84:
				sty(zpg());
				return 3;
			case 0x85:
				sta(zpg());
				return 3;
			case 0x86:
				stx(zpg());
				return 3;
			case 0x88:
				dey();
				return 2;
			case 0x8A:
				txa();
				return 2;
			case 0x8C:
				sty(abs());
				return 4;
			case 0x8D:
				sta(abs());
				return 4;
			case 0x8E:
				stx(abs());
				return 4;
			case 0x90:
				return bcc();
			case 0x91:
				sta(iy());
				return 6;
			case 0x94:
				sty(zpgx());
				return 4;
			case 0x95:
				sta(zpgx());
				return 4;
			case 0x96:
				stx(zpgy());
				return 4;
			case 0x98:
				tya();
				return 2;
			case 0x99:
				sta(absy());
				return 5;
			case 0x9A:
				txs();
				return 2;
			case 0x9D:
				sta(absx());
				return 5;
			case 0xA0:
				ldy(imm());
				return 2;
			case 0xA1:
				lda(ix());
				return 6;
			case 0xA2:
				ldx(imm());
				return 2;
			case 0xA4:
				ldy(zpg());
				return 3;
			case 0xA5:
				lda(zpg());
				return 3;
			case 0xA6:
				ldx(zpg());
				return 3;
			case 0xA8:
				tay();
				return 2;
			case 0xA9:
				lda(imm());
				return 2;
			case 0xAA:
				tax();
				return 2;
			case 0xAC:
				ldy(abs());
				return 4;
			case 0xAD:
				lda(abs());
				return 4;
			case 0xAE:
				ldx(abs());
				return 4;
			case 0xB0:
				return bcs();
			case 0xB1:
				lda(iy1());
				return 5;
			case 0xB4:
				ldy(zpgx());
				return 4;
			case 0xB5:
				lda(zpgx());
				return 4;
			case 0xB6:
				ldx(zpgy());
				return 4;
			case 0xB8:
				clv();
				return 2;
			case 0xB9:
				lda(absy1());
				return 4;
			case 0xBA:
				tsx();
				return 2;
			case 0xBC:
				ldy(absx1());
				return 4;
			case 0xBD:
				lda(absx1());
				return 4;
			case 0xBE:
				ldx(absy1());
				return 4;
			case 0xC0:
				cpy(imm());
				return 2;
			case 0xC1:
				cmp(ix());
				return 6;
			case 0xC4:
				cpy(zpg());
				return 3;
			case 0xC5:
				cmp(zpg());
				return 3;
			case 0xC6:
				dec(zpg());
				return 5;
			case 0xC8:
				iny();
				return 2;
			case 0xC9:
				cmp(imm());
				return 2;
			case 0xCA:
				dex();
				return 2;
			case 0xCC:
				cpy(abs());
				return 4;
			case 0xCD:
				cmp(abs());
				return 4;
			case 0xCE:
				dec(abs());
				return 6;
			case 0xD0:
				return bne();
			case 0xD1:
				cmp(iy1());
				return 5;
			case 0xD5:
				cmp(zpgx());
				return 4;
			case 0xD6:
				dec(zpgx());
				return 6;
			case 0xD8:
				cld();
				return 2;
			case 0xD9:
				cmp(absy1());
				return 4;
			case 0xDD:
				cmp(absx1());
				return 4;
			case 0xDE:
				dec(absx());
				return 7;
			case 0xE0:
				cpx(imm());
				return 2;
			case 0xE1:
				sbc(ix());
				return 6;
			case 0xE4:
				cpx(zpg());
				return 3;
			case 0xE5:
				sbc(zpg());
				return 3;
			case 0xE6:
				inc(zpg());
				return 5;
			case 0xE8:
				inx();
				return 2;
			case 0xE9:
				sbc(imm());
				return 2;
			case 0xEA:
				nop();
				return 2;
			case 0xEC:
				cpx(abs());
				return 4;
			case 0xED:
				sbc(abs());
				return 4;
			case 0xEE:
				inc(abs());
				return 6;
			case 0xF0:
				return beq();
			case 0xF1:
				sbc(iy1());
				return 5;
			case 0xF5:
				sbc(zpgx());
				return 4;
			case 0xF6:
				inc(zpgx());
				return 6;
			case 0xF8:
				sed();
				return 2;
			case 0xF9:
				sbc(absy1());
				return 4;
			case 0xFD:
				sbc(absx1());
				return 4;
			case 0xFE:
				inc(absx());
				return 7;

			default:
				return unsupport_instr(code);
		}
	}

	private int jmp_cond(boolean cond) {
		int rel = this.next();
		if (rel >= 128) rel = 0xffffff00 | rel;
		if (!cond) return 2;
		int pc = this.regs.getPC();
		regs.setPC(pc + rel);
		int p8 = (pc & 0xff) + rel;
		return (p8 >= 0 && p8 < 256) ? 3 : 4;
	}

	private int bcc() {
		return jmp_cond(!regs.isC());
	}

	private int bcs() {
		return jmp_cond(regs.isC());
	}

	private int beq() {
		return jmp_cond(regs.isZ());
	}

	private int bmi() {
		return jmp_cond(regs.isN());
	}

	private int bne() {
		return jmp_cond(!regs.isZ());
	}

	private int bpl() {
		return jmp_cond(!regs.isN());
	}

	private int bvc() {
		return jmp_cond(!regs.isV());
	}

	private int bvs() {
		return jmp_cond(regs.isV());
	}

	private void jmp_abs() {
		this.regs.setPC(next2());
	}

	private void jmp_indrect() {
		int addr = this.next2();
		int lo = read(addr);
		int hi = read((addr & 0xff00) + ((addr + 1) & 0xff));
		regs.setPC(lo | (hi << 8));
	}

	private void clc() {
		regs.setC(false);
	}

	private void cld() {
		regs.setD(false);
	}

	private void cli() {
		regs.setI(false);
	}

	private void clv() {
		regs.setV(false);
	}

	private void sec() {
		regs.setC(true);
	}

	private void sed() {
		regs.setD(true);
	}

	private void sei() {
		regs.setI(true);
	}

	private void tax() {
		this.set_sign_zero(regs.setXR(regs.getAC()));
	}

	private void tay() {
		this.set_sign_zero(regs.setYR(regs.getAC()));
	}

	private void tsx() {
		this.set_sign_zero(regs.setXR(this.regs.getSP()));
	}

	private void txa() {
		this.set_sign_zero(regs.setAC(regs.getXR()));
	}

	private void txs() {
		this.regs.setSP(regs.getXR());
	}

	private void tya() {
		this.set_sign_zero(regs.setAC(regs.getYR()));
	}

	private void push(int val) {
		int sp = this.regs.getSP();
		this.write(SP_OFFSET + sp, val);
		regs.setSP(regs.getSP() - 1);
	}

	private int pop() {
		int sp = regs.setSP(regs.getSP() + 1);
		return this.read(SP_OFFSET + sp);
	}

	private int reset() {
	    /*
	    for(int i=0;i<256;i++)
            mem[i].set(0);
        this.regs.getAC() = 0;
        this.regs.getXR() = 0;
        this.regs.getYR() = 0;
        this.regs.getSP() = 0xff;
        this.setS(0);*/
		regs.setSP(0xff);
		regs.setIR(0);
		regs.setI(true);
		regs.setPC(read2(RESET_ADDR));
		return 6;
	}

	private int brk(boolean softBreak, int irqAddr) {
		int pc = regs.getPC();
		if (softBreak) pc++;
		push(0xff & (pc >> 8));
		push(pc & 0xff);
		regs.setB(softBreak);
		push(regs.getFlags());
		regs.setI(true);
		regs.setPC(read2(irqAddr));
		return 7;
	}

	private void rti() {
		regs.setFlags(pop());
		int lo = pop();
		int hi = pop();
		regs.setPC(lo | (hi << 8));
	}

	private void jsr() {
		int addr = next2();
		int pc = regs.getPC() - 1;
		push((pc >> 8) & 0xff);
		push(pc & 0xff);
		regs.setPC(addr);
	}

	private void rts() {
		int lo = pop();
		int hi = pop();
		regs.setPC((lo + (hi << 8) + 1));
	}

	private void adc(AccessUnit op) {
		int val = op.get();
		int ac = this.regs.getAC();
		int c = regs.isC() ? 1 : 0;
		int temp = val + ac + c;
		if (supportD && regs.isD()) {
			this.current_cycles++;
			if (((ac & 0xf) + (val & 0xf) + c) > 9) temp += 6;
			regs.setV(0 == ((ac ^ val) & 0x80) && 0 != ((ac ^ temp) & 0x80));
			if (temp > 0x99) temp += 0x60;
			regs.setC(temp < 0 || temp > 0x99);
		} else {
			regs.setV(0 == ((ac ^ val) & 0x80) && 0 != ((ac ^ temp) & 0x80));
			regs.setC(temp < 0 || temp > 0xff);
		}
		this.set_sign_zero(regs.setAC(temp));
	}

	private void and(AccessUnit op) {
		this.set_sign_zero(regs.setAC(regs.getAC() & op.get()));
	}

	private void asl(AccessUnit op) {
		int src = op.get();
		regs.setC(0 != (src & 0x80));
		src <<= 1;
		src &= 0xff;
		set_sign_zero(src);
		op.set(src);
	}

	private void bit(AccessUnit op) {
		int src = op.get();
		regs.setN(0 != (src & 0x80));
		regs.setV(0 != (0x40 & src));
		regs.setZ(0 == (src & regs.getAC()));
	}

	private void cmp(int diff) {
		regs.setC(diff >= 0 && diff < 0x100);
		set_sign_zero(diff & 0xff);
	}

	private void cmp(AccessUnit op) {
		cmp(this.regs.getAC() - op.get());
	}

	private void cpx(AccessUnit op) {
		cmp(this.regs.getXR() - op.get());
	}

	private void cpy(AccessUnit op) {
		cmp(this.regs.getYR() - op.get());
	}

	private void dec(AccessUnit op) {
		int val = 0xff & (op.get() - 1);
		set_sign_zero(val);
		op.set(val);
	}

	private void dex() {
		this.set_sign_zero(regs.setXR(regs.getXR() - 1));
	}

	private void dey() {
		this.set_sign_zero(regs.setYR(regs.getYR() - 1));
	}

	private void inx() {
		this.set_sign_zero(regs.setXR(regs.getXR() + 1));
	}

	private void iny() {
		this.set_sign_zero(regs.setYR(regs.getYR() + 1));
	}

	private void eor(AccessUnit op) {
		this.set_sign_zero(regs.setAC(regs.getAC() ^ op.get()));

	}

	private void inc(AccessUnit op) {
		int val = 0xff & (op.get() + 1);
		set_sign_zero(val);
		op.set(val);
	}

	private void lda(AccessUnit op) {
		this.set_sign_zero(regs.setAC(op.get()));
	}

	private void ldx(AccessUnit op) {
		this.set_sign_zero(regs.setXR(op.get()));
	}

	private void ldy(AccessUnit op) {
		this.set_sign_zero(regs.setYR(op.get()));
	}

	private void lsr(AccessUnit op) {
		int src = op.get();
		regs.setC(0 != (src & 1));
		src >>= 1;
		set_sign_zero(src);
		op.set(src);
	}

	private void nop() {
	}

	private void ora(AccessUnit op) {
		this.set_sign_zero(regs.setAC(regs.getAC() | op.get()));
	}

	private void pha() {
		push(this.regs.getAC());
	}

	private void php() {
		push(regs.getFlags());
	}

	private void pla() {
		this.set_sign_zero(regs.setAC(pop()));
	}

	private void plp() {
		regs.setFlags(pop());
	}

	private void rol(AccessUnit op) {
		int src = op.get() << 1;
		if (regs.isC()) src |= 1;
		regs.setC(src > 0xff);
		src &= 0xff;
		set_sign_zero(src);
		op.set(src);
	}

	private void ror(AccessUnit op) {
		int src = op.get();
		if (regs.isC()) src |= 0x100;
		regs.setC(0 != (src & 1));
		src >>= 1;
		set_sign_zero(src & 0xff);
		op.set(src);
	}

	private void sbc(AccessUnit op) {
		int src = op.get();
		int ac = this.regs.getAC();
		int temp = ac - src - (regs.isC() ? 0 : 1);
		regs.setV(0 != ((ac ^ temp) & 0x80) && 0 != ((ac ^ src) & 0x80));
		if (regs.isD() && supportD) {
			this.current_cycles++;
			if (((ac & 0xf) - (regs.isC() ? 0 : 1)) < (src & 0xf)) temp -= 6;
			if (temp > 0x99) temp -= 0x60;
		}
		regs.setC(temp >= 0 && temp < 0x100);
		this.set_sign_zero(regs.setAC(temp));
	}

	private void sta(AccessUnit op) {
		op.set(this.regs.getAC());
	}

	private void stx(AccessUnit op) {
		op.set(this.regs.getXR());
	}

	private void sty(AccessUnit op) {
		op.set(this.regs.getYR());
	}

	private int unsupport_instr(int code) {
		//System.err.println("unsupport instr " + Util.b2s(code) + ", " +  this);
		switch (code) {
			case 0x1A: // NOP (Unofficial)
			case 0x3A: // NOP (Unofficial)
			case 0x5A: // NOP (Unofficial)
			case 0x7A: // NOP (Unofficial)
			case 0xDA: // NOP (Unofficial)
			case 0xFA: // NOP (Unofficial)
				return 2;
			case 0x80: // DOP (CYCLES 2)
			case 0x82: // DOP (CYCLES 2)
			case 0x89: // DOP (CYCLES 2)
			case 0xC2: // DOP (CYCLES 2)
			case 0xE2: // DOP (CYCLES 2)
				regs.setPC(regs.getPC() + 1);
				return 2;
			case 0x04: // DOP (CYCLES 3)
			case 0x44: // DOP (CYCLES 3)
			case 0x64: // DOP (CYCLES 3)
				regs.setPC(regs.getPC() + 1);
				return 3;
			case 0x14: // DOP (CYCLES 4)
			case 0x34: // DOP (CYCLES 4)
			case 0x54: // DOP (CYCLES 4)
			case 0x74: // DOP (CYCLES 4)
			case 0xD4: // DOP (CYCLES 4)
			case 0xF4: // DOP (CYCLES 4)
				regs.setPC(regs.getPC() + 1);
				return 4;
			case 0x0C: // TOP
			case 0x1C: // TOP
			case 0x3C: // TOP
			case 0x5C: // TOP
			case 0x7C: // TOP
			case 0xDC: // TOP
			case 0xFC: // TOP
				regs.setPC(regs.getPC() + 2);
				return 4;

			case 0x02:  /* JAM */
			case 0x12:  /* JAM */
			case 0x22:  /* JAM */
			case 0x32:  /* JAM */
			case 0x42:  /* JAM */
			case 0x52:  /* JAM */
			case 0x62:  /* JAM */
			case 0x72:  /* JAM */
			case 0x92:  /* JAM */
			case 0xB2:  /* JAM */
			case 0xD2:  /* JAM */
			case 0xF2:  /* JAM */
			default:
				regs.setPC(regs.getPC() - 1);//dead loop
				return 4;
		}
	}
}
